//
//  SerialManager.swift
//  Urso
//
//  Created by Cosmetic Cygia on 2024/6/12.
//

import Foundation
import IOKit
import IOKit.serial


func enableMotor(channel: UInt8) -> [UInt8] {
    //01 06 00 7C 00 9F
    var cmd    : [UInt8] = [channel]
    cmd.append(0x06)
    cmd.append(0x00)
    cmd.append(0x7C)
    cmd.append(0x00)
    cmd.append(0x9F)
    
    let crc = calculateCRC16Modbus(input: cmd)
    cmd.append(crc[0])
    cmd.append(crc[1])
    return cmd
}

func disableMotor(channel: UInt8) -> [UInt8] {
    //01 06 00 7C 00 9F
    var cmd    : [UInt8] = [channel]
    cmd.append(0x06)
    cmd.append(0x00)
    cmd.append(0x7C)
    cmd.append(0x00)
    cmd.append(0x9E)
    
    let crc = calculateCRC16Modbus(input: cmd)
    cmd.append(crc[0])
    cmd.append(crc[1])
    return cmd
}

func relativelyMove(channel: UInt8) -> [UInt8] {
    // 01 06 00 7C 00 66
    var cmd    : [UInt8] = [channel]
    cmd.append(0x06)
    cmd.append(0x00)
    cmd.append(0x7C)
    cmd.append(0x00)
    cmd.append(0x66)
    
    let crc = calculateCRC16Modbus(input: cmd)
    cmd.append(crc[0])
    cmd.append(crc[1])
    ELog.d(#function, cmd.description, #line)
    return cmd
}

func homeMotor(channel: UInt8, trigger: UInt8) -> [UInt8] {
    //01 10 00 7C 00 03 06 00 6E 00 31 00 4C 9D 67
    var cmd   : [UInt8] = [channel]
    cmd.append(0x10)
    cmd.append(0x00)
    cmd.append(0x7C)
    cmd.append(0x00)
    cmd.append(0x03)
    cmd.append(0x06)
    cmd.append(0x00)
    cmd.append(0x6E)
    cmd.append(0x00)
    cmd.append(trigger)
    cmd.append(0x00)
    cmd.append(0x4C)
    
    let crc = calculateCRC16Modbus(input: cmd)
    cmd.append(crc[0])
    cmd.append(crc[1])
    ELog.d(#function, cmd.description, #line)
    return cmd
}

func relativelyMove3() -> [UInt8] {
    var cmd    : [UInt8] = [0x03]
    cmd.append(0x06)
    cmd.append(0x00)
    cmd.append(0x7C)
    cmd.append(0x00)
    cmd.append(0x66)
    cmd.append(0xC9)
    cmd.append(0xDA)
    ELog.d(#function, cmd.description, #line)
    return cmd
}

func setSteps(channel: UInt8, steps:Int32) -> [UInt8] {
    // 01 10 00 1B 00 05 0A 02 58 02 58 09 C4 00 07 A1 42
    var cmd   : [UInt8] = [channel]
    cmd.append(0x10)
    cmd.append(0x00)
    cmd.append(0x1B)
    cmd.append(0x00)
    cmd.append(0x05)
    cmd.append(0x0A)
    cmd.append(0x02)
    cmd.append(0x58)
    cmd.append(0x02)
    cmd.append(0x58)
    cmd.append(0x09)
    cmd.append(0xC4)
    
    let _steps = int32ToByteArray(value: steps)
    
    cmd.append(_steps[3])
    cmd.append(_steps[2])
    cmd.append(_steps[1])
    cmd.append(_steps[0])
    
    let crc = calculateCRC16Modbus(input: cmd)
    cmd.append(crc[0])
    cmd.append(crc[1])
    ELog.d(#function, cmd.description, #line)
    return cmd
}


// Function to get the list of serial devices
func getSerialDeviceList() -> [String] {
    var deviceList: [String] = []
    
    let matchingDict = IOServiceMatching(kIOSerialBSDServiceValue)
    var iterator: io_iterator_t = 0
    let result = IOServiceGetMatchingServices(kIOMainPortDefault, matchingDict, &iterator)
    
    if result == KERN_SUCCESS {
        var service: io_object_t
        while (true) {
            service = IOIteratorNext(iterator)
            if service == 0 {
                break
            }
            
            let devicePath = IORegistryEntryCreateCFProperty(service, kIOCalloutDeviceKey as CFString, kCFAllocatorDefault, 0)
            if let path = devicePath?.takeRetainedValue() as? String {
                deviceList.append(path)
            }
            
            IOObjectRelease(service)
        }
        
        IOObjectRelease(iterator)
    }
    
    return deviceList
}

class SerialManager {
    
    var fileDescriptor: Int32 = -1
    
    let serialQueue = DispatchQueue(label: "serialQueue")
    
    func closeSerialDevice() {
        if fileDescriptor != -1 {
            close(fileDescriptor)
            fileDescriptor = -1
            ELog.d(String(describing: type(of: self)),#function, "Serial port closed", #line)
            PavaoManager.instance.saveLog(logLevel: LOG_LEVEL.info, logContent: "Serial port closed")
        } else {
            ELog.d(String(describing: type(of: self)),#function, "Serial port is not open", #line)
            PavaoManager.instance.saveLog(logLevel: LOG_LEVEL.info, logContent: "Serial port is not open")
        }
    }
    
    // Open the last serial device in the list
    func openSerialDevice(serialName: String) {
        
        serialQueue.async {
            self.fileDescriptor = open(serialName, O_RDWR | O_NOCTTY | O_NONBLOCK)
            if self.fileDescriptor != -1 {
                var attributes = termios()
                tcgetattr(self.fileDescriptor, &attributes)
                cfsetspeed(&attributes, speed_t(B9600))
                attributes.c_cflag = tcflag_t(CS8 | CLOCAL | CREAD)
                attributes.c_iflag = tcflag_t(IGNPAR)
                attributes.c_oflag = 0
                attributes.c_lflag = 0
                tcsetattr(self.fileDescriptor, TCSANOW, &attributes)
                ELog.d(#function, "Serial port \(serialName) is open", #line)
                PavaoManager.instance.saveLog(logLevel: LOG_LEVEL.info, logContent: "Serial port \(serialName) is open")
                while true {
                    if let receivedData = self.receiveData(self.fileDescriptor), let receivedString = String(data: receivedData, encoding: .utf8) {
//                        if #available(macOS 12, *) {
//                            ELog.d(#function, "Received data: \(receivedString)", #line)
//                        } else {
                            ELog.d(#function, "Received data: \(receivedString)", #line)
//                        }
                        
                    }
                }
                
            } else {
                ELog.e(String(describing: type(of: self)),#function, "Failed to open serial port \(serialName) ", #line)
                PavaoManager.instance.saveLog(logLevel: LOG_LEVEL.error, logContent: "Failed to open serial port \(serialName) ")
            }
        }
    }
    
    
    // Function to send data through the serial port
    func sendData(_ fileDescriptor: Int32, _ data: Data) {
        _ = write(fileDescriptor, (data as NSData).bytes.bindMemory(to: UInt8.self, capacity: data.count), data.count)
    }
    // Function to send data through the serial port
    func sendData(_ fileDescriptor: Int32, data: [UInt8]) {
        
        let data_ = Data(data)
        _ = write(fileDescriptor, (data_ as NSData).bytes.bindMemory(to: UInt8.self, capacity: data.count), data.count)
    }
    
    // Function to receive data from the serial port
    func receiveData(_ fileDescriptor: Int32) -> Data? {
        var buffer = [UInt8](repeating: 0, count: 1024)
        let bytesRead = read(fileDescriptor, &buffer, buffer.count)
        if bytesRead > 0 {
            return Data(bytes: buffer, count: bytesRead)
        }
        return nil
    }
}
